use crate::model::user::UserId;
use std::{
    cmp::Ordering,
    error::Error,
    io::{self, ErrorKind},
    time::{Duration, SystemTime, UNIX_EPOCH},
};

use hmac::{Hmac, Mac};
use jwt::{SignWithKey, VerifyWithKey};
use rand::{rngs::ThreadRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use tokio::{
    fs::{File, OpenOptions},
    io::{AsyncReadExt, AsyncWriteExt},
    sync::RwLock,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenInformation {
    #[serde(rename = "userId")]
    pub user_id: UserId,
    #[serde(rename = "iat")]
    pub issued_time: u64,
}

pub enum TokenVerifyStatus {
    Verified(TokenInformation),
    Expired,
    Invalid,
    TimeInvalid,
}

const GENERATE_KEY_LENGTH: usize = 256;
const KEY_FILE_NAME: &str = "token_key.bin";
static KEY: RwLock<Option<Hmac<Sha256>>> = RwLock::const_new(None);
pub static TOKEN_VALID_TIME: Duration = Duration::from_secs(3600);

async fn generate_key() -> Result<[u8; GENERATE_KEY_LENGTH], rand::Error> {
    let mut rand = ThreadRng::default();
    let mut key = [0; GENERATE_KEY_LENGTH];
    rand.try_fill_bytes(&mut key)?;
    Ok(key)
}

#[allow(unused)]
#[derive(Debug)]
pub enum LoadKeyError {
    ReadKey(io::Error),
    WriteKey(io::Error),
    Generate(rand::Error),
}

async fn read_key() -> io::Result<Option<Vec<u8>>> {
    let mut key_file = match File::open(KEY_FILE_NAME).await {
        Ok(file) => file,
        Err(err) => match err.kind() {
            ErrorKind::NotFound => return Ok(None),
            _ => return Err(err),
        },
    };

    let mut buf = Vec::new();
    key_file.read_to_end(&mut buf).await?;

    Ok(Some(buf))
}

async fn write_key(key: &[u8]) -> io::Result<()> {
    let mut key_file = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(KEY_FILE_NAME)
        .await?;

    key_file.write_all(key).await?;
    Ok(())
}

pub async fn load_key() -> Result<(), LoadKeyError> {
    let key = match read_key().await {
        Ok(result) => match result {
            Some(key) => key,
            None => {
                println!("Token key not found, generate one.");
                let key = match generate_key().await {
                    Ok(key) => key,
                    Err(err) => return Err(LoadKeyError::Generate(err)),
                };
                if let Err(err) = write_key(&key).await {
                    return Err(LoadKeyError::WriteKey(err));
                }
                Vec::from(key)
            }
        },
        Err(err) => return Err(LoadKeyError::ReadKey(err)),
    };

    install_key(&key).await;
    Ok(())
}

pub async fn install_key(key: &[u8]) {
    let mut key_lock = KEY.write().await;
    *key_lock = Some(Hmac::new_from_slice(key).unwrap());
}

pub async fn sign_token(info: &TokenInformation) -> Result<String, Box<dyn Error>> {
    let key_lock = KEY.read().await;
    let key = (*key_lock).as_ref().expect("No key installed");
    Ok(info.sign_with_key(key)?)
}

pub async fn verify_token(token: &str) -> TokenVerifyStatus {
    let key_lock = KEY.read().await;
    let key = (*key_lock).as_ref().expect("No key installed");
    match token.verify_with_key(key) {
        Ok(info) => {
            let info: TokenInformation = info;
            let issued_time = UNIX_EPOCH + Duration::from_secs(info.issued_time);
            match SystemTime::now().duration_since(issued_time) {
                Ok(duration) => match duration.cmp(&TOKEN_VALID_TIME) {
                    Ordering::Equal | Ordering::Less => TokenVerifyStatus::Verified(info),
                    Ordering::Greater => TokenVerifyStatus::Expired,
                },
                Err(_) => TokenVerifyStatus::TimeInvalid,
            }
        }
        Err(_) => TokenVerifyStatus::Invalid,
    }
}
